Skip to content

Add extension version pinning with CodexConductor and CLI#27

Draft
JonahBraun wants to merge 18 commits intomasterfrom
feature/profile-conductor
Draft

Add extension version pinning with CodexConductor and CLI#27
JonahBraun wants to merge 18 commits intomasterfrom
feature/profile-conductor

Conversation

@JonahBraun
Copy link
Copy Markdown

Adds project-scoped extension version pinning so admins can lock a project to a specific codex-editor build via metadata.json.

Changes

  • Add CodexConductor workbench contribution that enforces pins at startup via VS Code profile isolation, with mid-session detection, reload-loop circuit breaker, and automatic profile cleanup
  • Add codex pin list/add/remove Rust CLI commands for managing pins in project metadata
  • Add codex-cli symlink (points to codex-tunnel) for direct Rust CLI access without the Node wrapper
  • Replace hardcoded extension download logic with JSON-driven bundle-extensions.json config
  • Fix local dev build environment (SHOULD_BUILD_REH exports)
  • Move development docs to AGENTS.md with updated build/patch documentation

Design docs

Test plan

  • Full local build succeeds (./dev/build.sh)
  • All 35 patches apply cleanly
  • codex pin --help outputs subcommand usage
  • Pin a project and verify profile switch on open
  • Mid-session pin change via sync triggers reload prompt

Add SHOULD_BUILD_REH and SHOULD_BUILD_REH_WEB exports to dev/build.sh
to prevent "unbound variable" errors during local builds. Add .claude/
and fv to .gitignore.
Replace hardcoded extension download logic in get-extensions.sh with a
declarative bundle-extensions.json config. Extensions are downloaded as
pre-built VSIXs from GitHub Releases via `gh release download` and
unpacked into vscode/extensions/ during the build.
A workbench contribution baked into the Codex shell that enforces
project-scoped extension version pins. Reads pin declarations from
project metadata.json (or Frontier's workspaceState), downloads VSIXs
from GitHub Release URLs, installs them into deterministic VS Code
profiles, and switches the extension host.

Includes mid-session pin detection via IStorageService signals, a
3-cycle reload-loop circuit breaker, 14-day automatic profile cleanup,
and a progress notification UX with "Reload Codex When Ready".
Adds `codex pin list/add/remove` subcommands to the Rust CLI for
managing extension version pins in project metadata.json. The `add`
command downloads a remote VSIX, extracts the extension ID and version
from its package.json, and writes the pin entry.

The patch registers `pin` as a native CLI command in argv.ts with
Node-to-Rust hand-off, adds PinningError to the error types, and
refactors the macOS "Install Shell Command" to create both a `codex`
and `codex-cli` symlink (the latter pointing directly to codex-tunnel
for direct Rust CLI access without the Node wrapper).

pin.rs is delivered via source overlay; the patch modifies existing
VS Code files (args, argv, nativeHostMainService). The patch is built
on the baseline of binary-name.patch which it depends on.
Move development instructions to AGENTS.md (readable by both humans and
AI agents). CLAUDE.md becomes a symlink to AGENTS.md for backward
compatibility.
…ex components

Remove redundant tutorial-style content and stale merge strategy details.
Add build pipeline diagram, overlay vs patch guidance, patch dependency
table, and documentation for CodexConductor, CLI pin commands, and
extension bundling.
…hardcoded "code"

The Rust CLI's version_manager.rs had five hardcoded references to "code" as the
editor binary name. This caused codex-tunnel commands (e.g. pin) to fail with
"No such file or directory" when looking for bin/code instead of bin/codex.

## Changes
- Update DESKTOP_CLI_RELATIVE_PATH to use concatcp! with APPLICATION_NAME
- Update detect_installed_program /Applications/ fast path
- Update detect_installed_program system_profiler fallback path
@JonahBraun JonahBraun marked this pull request as draft March 25, 2026 23:33
@JonahBraun JonahBraun force-pushed the feature/profile-conductor branch from 9c0e6a4 to ee5f1b3 Compare March 26, 2026 15:48
NativeExtensionManagementService.downloadVsix() intercepts install()
calls in the renderer and downloads via browser fetch(), which fails
for GitHub release URLs due to CORS on the 302 redirect. Route the
install call through the shared process IPC channel directly, where
Node.js networking handles redirects without CORS restrictions.

Also adds retry logic with backoff, profile cleanup on failure, and
richer error reporting with Copy Error Report action.
…ement

Resolved a reliability issue where extension version pin enforcement would enter
an infinite reload loop, especially when running in extension development mode
or when custom editors vetoed the extension host restart.

Core Changes:
- Implement "Authoritative Reload": Patched VS Code core to allow the reload()
  IPC command to accept an explicit forceProfile name.
- Patch windowImpl.ts to respect the passed profile name and explicitly
  revive workspace URIs during lookup to bypass Main process stale-cache issues.
- Patch windowsMainService.ts to allow profile-workspace associations to be
  persisted even when launched with --extensionDevelopmentPath.
- Update CodexConductor to use the authoritative reload signal and explicitly
  call resetWorkspaces() before switching to prevent lookup conflicts.

Build System:
- Fix build_cli.sh to use mkdir -p when preparing OpenSSL to prevent
  spurious build failures.

Documentation:
- Updated AGENTS.md with details on the authoritative reload and robustness
  features.
The forceProfile authoritative reload path looks up the profile from
Main process memory, not disk. The timeout was unnecessary since the
profile association is already in-memory when reload fires.
Adds a Command Palette command (Codex: Manage Extension Pins) for in-editor
pin management. Supports viewing required/pinned extensions, adding pins from
VSIX URLs or GitHub release pages, removing pins, and syncing via Frontier.

Also adds GitHub release page URL resolution to the Rust CLI pin command, and
fixes metadata.json serialization to use 4-space indent matching codex-editor.

## Changes
- New codexPinManager.ts workbench contribution with QuickPick hub UI
- CLI resolve_vsix_url() resolves release page URLs to VSIX download URLs
- CLI write_metadata uses 4-space indent (matches codex-editor convention)
…nvalid metadata

Previously, calling initialize() multiple times leaked storage listeners, non-FOLDER
workspaces left users stranded on conductor profiles, and missing or invalid
metadata.json caused early returns that skipped revertIfPatchBuild().
…profiles

The CONDUCTOR_PROFILE_PATTERN regex didn't match pre-release version suffixes
(e.g. codex-editor-v0.24.0-pr816-1148908f), so revertIfPatchBuild() silently
skipped revert when opening a project without pins. Setting the 'repo-pinned'
icon on creation provides a reliable, self-describing marker on the profile
itself.
After removing a pin, metadata.json retains an empty `pinnedExtensions: {}`. `readPinsSnapshot()` treated this as truthy, returning `"{}"` instead of `undefined`, causing `resolveProfileName()` to error on `undefined.includes('.')`.
`setProfileForWorkspace` internally updates `currentProfile` even when the extension host vetos the switch. The post-call ID check then incorrectly reports "already on target" and skips the authoritative reload, causing duplicate extension registrations and a blank sidebar. Capture the profile ID before the call instead.
… all boundaries

Consolidates duplicate PinnedExtensionEntry, PinnedExtensions, RequiredExtensions, and ProjectMetadata declarations into a shared module. Adds parsePinnedExtensions() which validates entry shape (string version and url) and drops malformed entries, replacing all raw JSON.parse casts.
VS Code stores an extension's entire workspaceState as a single JSON blob
under the extension ID key. The conductor was reading a dotted subkey
(frontier-rnd.frontier-authentication.remotePinnedExtensions) that never
existed. Read the blob key and extract the remotePinnedExtensions field
from within it. This fixes the entire storage-based flow: initial
enforcement from remote pins, mid-session detection, and sync deadlock
resolution.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant